package org.springframework.validation.util;

import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import javax.servlet.jsp.el.ELException;
import javax.servlet.jsp.el.VariableResolver;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeanWrapperImpl;

public class ModelAndMapsVariableResolver implements VariableResolver {
	
	protected static Log log = LogFactory.getLog(ModelAndMapsVariableResolver.class);
	protected Map<String, Map<String, Object>> contextMaps = new HashMap<String, Map<String, Object>>();
	protected LinkedList<String> contextOrder = new LinkedList<String>();
	
	public static final String MODEL = "model";

	Object modelObject = null;
	
	public ModelAndMapsVariableResolver(Object model) {
		this.modelObject = model;
	}
	
	protected void addMapContext(String contextName, Map<String, Object> context) {
		if (contextMaps.containsKey(contextName)) {
			throw new IllegalArgumentException("Context already registered; " + contextName);
		}
		contextOrder.addLast(contextName);
		contextMaps.put(contextName, context);
	}
	
	public Object resolveVariableSafely(String name) {
		try {
			return resolveVariable(name);
		} catch (ELException ele) {
			log.error("Couldn't resolve variable. ", ele);
			return null;
		}
	}
	
	public Object resolveVariable(String name) throws ELException { 
		
		// check for model prefix.
		if (name.startsWith(MODEL + ".")) {
			return getFromModel(removePrefix(name));
		} 
		// check context prefixes
		for (String contextName : contextOrder) {
			if (name.startsWith(contextName + ".")) {
				return getFromContext(name, contextName);
			}
		}
		// no prefix, check model.
		Object candidate = getFromModel(name);
		if (candidate != null) {
			return candidate;
		}
		// not on model, check contexts individually.
		for (String contextName: contextOrder) {
			candidate = getFromContext(name, contextName);
			if (candidate != null) {
				return candidate;
			}
		}
		// out of things to check :(
		return null;
	}
	
	public Object getFromModel(String name) {
		if (modelObject == null) {
			return null;
		}
		BeanWrapperImpl beanWrapper = new BeanWrapperImpl(modelObject);
		return beanWrapper.isReadableProperty(name) ? 
				beanWrapper.getPropertyValue(name) : null;
	}
	
	public String removePrefix(String name) {
		return name.substring(name.indexOf(".") + 1);
	}
	
	public String getPrefix(String name) {
		return name.substring(0, name.indexOf("."));
	}
	
	protected Object getFromContext(String name, String contextName) {
		Map<String, Object> map = contextMaps.get(contextName);
		if (name.contains(".")) {
			String beanName = getPrefix(name);
			String path = removePrefix(name);
			if (map != null && map.containsKey(beanName)) {
				Object bean = map.get(beanName);
				return new BeanWrapperImpl(bean).getPropertyValue(path);
			} else {
				return null;
			}
		} else {
			if (map != null && map.containsKey(name)) {
				return map.get(name);
			} else {
				return null;
			}
		}
	}
	

}
